Option Explicit

' ======================================================================
' 执行所有测试用例
' ======================================================================
Public Sub test_all_case()
    ' 测试: 获取当前解析结果的类型
    test_getType
    ' 测试: 解析开始部分的作用域
    test_parseStartCompositionEnv
    ' 测试: 解析sql的各个组成部分
    test_parseSubCompositionEnv
End Sub

' ======================================================================
' 测试通用处理
' ======================================================================

' 创建一个格式化器的 sql 解析器
Private Function createSqlFormatterAnalyzer() As SqlFormatterAnalyzer
    Dim config As SqlFormatterConfiguration
    Set config = New SqlFormatterConfiguration

    Dim result As SqlFormatterAnalyzer
    Set result = New SqlFormatterAnalyzer

    result.Init config

    Set createSqlFormatterAnalyzer = result
End Function

' 创建一个扫描器
Private Function getObjArrayRefScanner(ParamArray tokens() As Variant) As ObjArrayRefScanner
    Dim i As Integer
    Dim t As SqlToken

    Dim tokenCount As Integer
    tokenCount = UBound(tokens)

    ' 创建一个和参数数量相同的数组引用
    Dim meanTokens As IObjArrayRef
    Set meanTokens = UtilsArrayRef.createRealObjArrayRef(tokenCount)

    ' 遍历参数，并保持到数组引用
    For i = 0 To UBound(tokens)
        Set t = tokens(i)

        meanTokens.Add t
    Next

    ' 创建扫描器
    Dim scanner As ObjArrayRefScanner
    Set scanner = New ObjArrayRefScanner
    scanner.Init meanTokens

    ' return scanner
    Set getObjArrayRefScanner = scanner
End Function

' 向一个数组引用中添加元素
Private Sub addElemToArray(ByRef arr As IObjArrayRef, ParamArray tokens() As Variant)
    Dim i As Integer
    Dim t As SqlToken

    Dim tokenCount As Integer
    tokenCount = UBound(tokens)

    ' 遍历参数，并保持到数组引用
    For i = 0 To UBound(tokens)
        Set t = tokens(i)

        arr.Add t
    Next
End Sub

' 获取一个 [;] 元素
Private Function getSemicolonToken() As SqlToken
    Set getSemicolonToken = New SqlToken
    getSemicolonToken.Init ";", SqlTokenType.SEMICOLON
End Function


' 获取一个 recursive 元素
Private Function getRecursiveToken() As SqlToken
    Set getRecursiveToken = New SqlToken
    getRecursiveToken.Init "recursive", SqlTokenType.KEY_RECURSIVE
End Function

' 获取一个 from 元素
Private Function getFromToken() As SqlToken
    Set getFromToken = New SqlToken
    getFromToken.Init "from", SqlTokenType.KEY_FROM
End Function

' 获取一个 cast 元素
Private Function getCastToken() As SqlToken
    Set getCastToken = New SqlToken
    getCastToken.Init "cast", SqlTokenType.KEY_CAST
End Function

' 创建一个字符串 token
Private Function createStrToken(ByVal v As String) As SqlToken
    Set createStrToken = New SqlToken
    createStrToken.Init "'" & v & "'", SqlTokenType.STR_STR
End Function

' where 关键字
Private Function getWhereToken() As SqlToken
    Set getWhereToken = New SqlToken
    getWhereToken.Init "where", SqlTokenType.KEY_WHERE
End Function

' having 关键字
Private Function getHavingToken() As SqlToken
    Set getHavingToken = New SqlToken
    getHavingToken.Init "having", SqlTokenType.KEY_HAVING
End Function

' 获取 duplicate 关键字
Private Function getDuplicateToken() As SqlToken
    Set getDuplicateToken = New SqlToken
    getDuplicateToken.Init "duplicate", SqlTokenType.KEY_DUPLICATE
End Function

' 获取 update 关键字
Private Function getUpdateToken() As SqlToken
    Set getUpdateToken = New SqlToken
    getUpdateToken.Init "update", SqlTokenType.KEY_UPDATE
End Function

' on 关键字
Private Function getOnToken() As SqlToken
    Set getOnToken = New SqlToken
    getOnToken.Init "on", SqlTokenType.KEY_ON
End Function

' case 运算符
Private Function getCaseToken() As SqlToken
    Set getCaseToken = New SqlToken
    getCaseToken.Init "case", SqlTokenType.KEY_CASE
End Function

' when 运算符
Private Function getWhenToken() As SqlToken
    Set getWhenToken = New SqlToken
    getWhenToken.Init "when", SqlTokenType.KEY_WHEN
End Function

' then 运算符
Private Function getThenToken() As SqlToken
    Set getThenToken = New SqlToken
    getThenToken.Init "then", SqlTokenType.KEY_THEN
End Function

' else 运算符
Private Function getElseToken() As SqlToken
    Set getElseToken = New SqlToken
    getElseToken.Init "else", SqlTokenType.KEY_ELSE
End Function

' end 运算符
Private Function getEndToken() As SqlToken
    Set getEndToken = New SqlToken
    getEndToken.Init "end", SqlTokenType.KEY_END
End Function

' ASC 运算符
Private Function getAscToken() As SqlToken
    Set getAscToken = New SqlToken
    getAscToken.Init "asc", SqlTokenType.KEY_ASC
End Function

' DESC 运算符
Private Function getDescToken() As SqlToken
    Set getDescToken = New SqlToken
    getDescToken.Init "desc", SqlTokenType.KEY_DESC
End Function

' || 运算符
Private Function getStrConcatToken() As SqlToken
    Set getStrConcatToken = New SqlToken
    getStrConcatToken.Init "||", SqlTokenType.OPERA_STR_CONCAT
End Function

' + 运算符
Private Function getPlusToken() As SqlToken
    Set getPlusToken = New SqlToken
    getPlusToken.Init "+", SqlTokenType.OPERA_PLUS
End Function

' - 运算符
Private Function getMinusToken() As SqlToken
    Set getMinusToken = New SqlToken
    getMinusToken.Init "-", SqlTokenType.OPERA_MINUS
End Function

' 创建一个数值元素
Private Function createNumToken(ByVal v As String) As SqlToken
    Set createNumToken = New SqlToken
    createNumToken.Init v, SqlTokenType.STR_NUM
End Function

' 获取一个 true 元素
Private Function getTrueToken() As SqlToken
    Set getTrueToken = New SqlToken
    getTrueToken.Init "true", SqlTokenType.KEY_TRUE
End Function

' 获取一个 false 元素
Private Function getFalseToken() As SqlToken
    Set getFalseToken = New SqlToken
    getFalseToken.Init "false", SqlTokenType.KEY_FALSE
End Function

' 获取一个 null 元素
Private Function getNullToken() As SqlToken
    Set getNullToken = New SqlToken
    getNullToken.Init "null", SqlTokenType.KEY_NULL
End Function

' 获取 * 元素
Private Function getAsteriskToken() As SqlToken
    Set getAsteriskToken = New SqlToken
    getAsteriskToken.Init "*", SqlTokenType.ASTERISK
End Function

' 获取 binary 关键字
Private Function getBinaryToken() As SqlToken
    Set getBinaryToken = New SqlToken
    getBinaryToken.Init "binary", SqlTokenType.KEY_BINARY
End Function

' 获取 order by 关键字
Private Function getOrderByToken() As SqlToken
    Set getOrderByToken = New SqlToken
    getOrderByToken.Init "order by", SqlTokenType.KEY_ORDER_BY
End Function

' 获取 group by 关键字
Private Function getGroupByToken() As SqlToken
    Set getGroupByToken = New SqlToken
    getGroupByToken.Init "group by", SqlTokenType.KEY_GROUP_BY
End Function

' 获取 with 关键字
Private Function getWithToken() As SqlToken
    Set getWithToken = New SqlToken
    getWithToken.Init "with", SqlTokenType.KEY_WITH
End Function

' 获取 ROLLUP 关键字
Private Function getRollupToken() As SqlToken
    Set getRollupToken = New SqlToken
    getRollupToken.Init "rollup", SqlTokenType.KEY_ROLLUP
End Function

' 获取 offset 关键字
Private Function getOffsetToken() As SqlToken
    Set getOffsetToken = New SqlToken
    getOffsetToken.Init "offset", SqlTokenType.KEY_OFFSET
End Function

' 获取 limit 关键字
Private Function getLimitToken() As SqlToken
    Set getLimitToken = New SqlToken
    getLimitToken.Init "limit", SqlTokenType.KEY_LIMIT
End Function

' 获取 is 关键字
Private Function getIsToken() As SqlToken
    Set getIsToken = New SqlToken
    getIsToken.Init "is", SqlTokenType.KEY_IS
End Function

' 获取 not 关键字
Private Function getNotToken() As SqlToken
    Set getNotToken = New SqlToken
    getNotToken.Init "not", SqlTokenType.KEY_NOT
End Function

' 获取 union 关键字
Private Function getUnionToken() As SqlToken
    Set getUnionToken = New SqlToken
    getUnionToken.Init "union", SqlTokenType.KEY_UNION
End Function

' 获取 select 关键字
Private Function getSelectToken() As SqlToken
    Set getSelectToken = New SqlToken
    getSelectToken.Init "select", SqlTokenType.KEY_SELECT
End Function

' 获取 distinct 关键字
Private Function getDistinctToken() As SqlToken
    Set getDistinctToken = New SqlToken
    getDistinctToken.Init "distinct", SqlTokenType.KEY_DISTINCT
End Function

' 获取 all 关键字
Private Function getAllToken() As SqlToken
    Set getAllToken = New SqlToken
    getAllToken.Init "all", SqlTokenType.KEY_ALL
End Function

' 获取 join 关键字
Private Function getJoinToken() As SqlToken
    Set getJoinToken = New SqlToken
    getJoinToken.Init "join", SqlTokenType.KEY_JOIN
End Function

' 获取 left 关键字
Private Function getLeftToken() As SqlToken
    Set getLeftToken = New SqlToken
    getLeftToken.Init "left", SqlTokenType.KEY_LEFT
End Function

' 获取 right 关键字
Private Function getRightToken() As SqlToken
    Set getRightToken = New SqlToken
    getRightToken.Init "right", SqlTokenType.KEY_RIGHT
End Function

' 获取 inner 关键字
Private Function getInnerToken() As SqlToken
    Set getInnerToken = New SqlToken
    getInnerToken.Init "inner", SqlTokenType.KEY_INNER
End Function

' 获取 cross 关键字
Private Function getCrossToken() As SqlToken
    Set getCrossToken = New SqlToken
    getCrossToken.Init "cross", SqlTokenType.KEY_CROSS
End Function

' 获取 out 关键字
Private Function getOuterToken() As SqlToken
    Set getOuterToken = New SqlToken
    getOuterToken.Init "outer", SqlTokenType.KEY_OUTER
End Function

' 获取 use 关键字
Private Function getUseToken() As SqlToken
    Set getUseToken = New SqlToken
    getUseToken.Init "use", SqlTokenType.KEY_USE
End Function

' 获取 index 关键字
Private Function getIndexToken() As SqlToken
    Set getIndexToken = New SqlToken
    getIndexToken.Init "index", SqlTokenType.KEY_INDEX
End Function

' 获取 ignore 关键字
Private Function getIgnoreToken() As SqlToken
    Set getIgnoreToken = New SqlToken
    getIgnoreToken.Init "ignore", SqlTokenType.KEY_IGNORE
End Function

' 获取 key 关键字
Private Function getKeyToken() As SqlToken
    Set getKeyToken = New SqlToken
    getKeyToken.Init "key", SqlTokenType.KEY_KEY
End Function

' 获取 for 关键字
Private Function getForToken() As SqlToken
    Set getForToken = New SqlToken
    getForToken.Init "for", SqlTokenType.KEY_FOR
End Function

' 获取 force 关键字
Private Function getForceToken() As SqlToken
    Set getForceToken = New SqlToken
    getForceToken.Init "force", SqlTokenType.KEY_FORCE
End Function

' 获取 as 关键字
Private Function getAsToken() As SqlToken
    Set getAsToken = New SqlToken
    getAsToken.Init "as", SqlTokenType.KEY_AS
End Function

' 获取左括号
Private Function getLeftParenthesisToken() As SqlToken
    Set getLeftParenthesisToken = New SqlToken
    getLeftParenthesisToken.Init "(", SqlTokenType.LEFT_PARENTHESIS
End Function

' 获取右括号
Private Function getRightParenthesisToken() As SqlToken
    Set getRightParenthesisToken = New SqlToken
    getRightParenthesisToken.Init ")", SqlTokenType.RIGHT_PARENTHESIS
End Function

' 获取 partition 关键字
Private Function getPartitionToken() As SqlToken
    Set getPartitionToken = New SqlToken
    getPartitionToken.Init "partition", SqlTokenType.KEY_PARTITION
End Function

' 获取逻辑运算符: and
Private Function getAndToken() As SqlToken
    Set getAndToken = New SqlToken
    getAndToken.Init "and", SqlTokenType.KEY_AND
End Function

' 获取一个条件运算符: <=
Private Function getLeConditionOperator() As SqlToken
    Set getLeConditionOperator = New SqlToken
    getLeConditionOperator.Init "<=", SqlTokenType.OPERA_LE
End Function

' 获取一个任意名称的 STR_NAME token
Private Function getStrNameToken(ByRef s As String) As SqlToken
    Set getStrNameToken = New SqlToken
    getStrNameToken.Init s, SqlTokenType.STR_NAME
End Function

' 获取表名 token
Private Function getTableNameToken() As SqlToken
    Set getTableNameToken = New SqlToken
    getTableNameToken.Init TEST_TABLE_NAME, SqlTokenType.STR_NAME
End Function

' 获取 [.] token
Private Function getPointToken() As SqlToken
    Set getPointToken = New SqlToken
    getPointToken.Init TEST_POINT, SqlTokenType.POINT
End Function

' 获取列名 token
Private Function getColNameToken() As SqlToken
    Set getColNameToken = New SqlToken
    getColNameToken.Init TEST_COL_NAME, SqlTokenType.STR_NAME
End Function

' 创建和前一个元素在同一行的注释
Private Function createShareLineCommentToken(ByRef v As String) As SqlToken
    Set createShareLineCommentToken = New SqlToken
    createShareLineCommentToken.Init v, SqlTokenType.SHARE_LINE_COMMENT
End Function

' 获取一个单行的注释
Private Function createSingleLineCommentToken(ByRef v As String) As SqlToken
    Set createSingleLineCommentToken = New SqlToken
    createSingleLineCommentToken.Init v, SqlTokenType.SINGLE_LINE_COMMENT
End Function

' 获取和前一个元素在同一行的注释
Private Function getShareLineCommentToken() As SqlToken
    Set getShareLineCommentToken = New SqlToken
    getShareLineCommentToken.Init TEST_SHARE_LINE_COMMENT, SqlTokenType.SHARE_LINE_COMMENT
End Function

' 获取一个单行的注释
Private Function getSingleLineCommentToken() As SqlToken
    Set getSingleLineCommentToken = New SqlToken
    getSingleLineCommentToken.Init TEST_SINGLE_LINE_COMMENT, SqlTokenType.SINGLE_LINE_COMMENT
End Function

' 获取一个逗号的元素
Private Function getCommaToken() As SqlToken
    Set getCommaToken = New SqlToken
    getCommaToken.Init ",", SqlTokenType.COMMA
End Function

' 生成一个函数名称元素
Private Function createFuncNameToken(ByVal funcName As String) As SqlToken
    Set createFuncNameToken = New SqlToken
    createFuncNameToken.Init funcName, SqlTokenType.STR_FUNC
End Function

' 获取 CURRENT_TIMESTAMP 元素
Private Function getCurrentTimestampToken() As SqlToken
    Set getCurrentTimestampToken = New SqlToken
    getCurrentTimestampToken.Init "CURRENT_TIMESTAMP", SqlTokenType.CONST_CURRENT_TIMESTAMP
End Function

' 获取 exists 元素
Private Function getExistsToken() As SqlToken
    Set getExistsToken = New SqlToken
    getExistsToken.Init "exists", SqlTokenType.KEY_EXISTS
End Function


' ======================================================================
' 类方法的测试
' ======================================================================

' 测试: 获取当前解析结果的类型
Private Sub test_getType()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim selectAnalyzer As SqlFormatterSelectSubEAnalyzer
    Set selectAnalyzer = New SqlFormatterSelectSubEAnalyzer
    selectAnalyzer.Init analyzer

    Debug.Assert selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_getType = SqlTokenType.KEY_SELECT
End Sub

' 测试: 解析开始部分的作用域
' 参考： Test___SqlFormatterAnalyzer_analyze_env.bas.vb 下的的 test_parseSelectItemsEnv
'      这里只测试一个方法，检查能不能返回解析结果对象
Private Sub test_parseStartCompositionEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim selectAnalyzer As SqlFormatterSelectSubEAnalyzer
    Set selectAnalyzer = New SqlFormatterSelectSubEAnalyzer
    selectAnalyzer.Init analyzer

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef

    ' ==============================
    ' 1. 扫描器内: select
    '             + STR_NAME + [+]
    '                      + case
    '                      + when + STR_NAME + Then + STR_NAME
    '                      + end
    '             + [,]
    '             + STR_NAME + <= + STR_NAME + and + STR_NAME + <= + STR_NAME
    '             + UNION
    '    测试: 解析多行列对象 + 一个条件对象
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getSelectToken, _
        _
        getStrNameToken("y1"), getPlusToken, _
        getCaseToken, _
        getWhenToken, getStrNameToken("x1"), getThenToken, getStrNameToken("x2"), _
        getEndToken, _
        _
        getCommaToken, _
        _
        getStrNameToken("y1"), getLeConditionOperator, getStrNameToken("y2"), _
        getAndToken, getStrNameToken("y3"), getLeConditionOperator, getStrNameToken("y4"), _
        _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseStartCompositionEnv(scanner)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 7
    Debug.Assert strArr.Item(0) = "select"
    Debug.Assert strArr.Item(1) = "    y1 +"
    Debug.Assert strArr.Item(2) = "        case"
    Debug.Assert strArr.Item(3) = "            when x1 then x2"
    Debug.Assert strArr.Item(4) = "        end,"
    Debug.Assert strArr.Item(5) = "    y1 <= y2"
    Debug.Assert strArr.Item(6) = "    and y3 <= y4"


End Sub

' 测试: 解析select的各个组成部分
Private Sub test_parseSubCompositionEnv()
    Dim analyzer As SqlFormatterAnalyzer
    Set analyzer = createSqlFormatterAnalyzer

    Dim config As SqlFormatterConfiguration
    Set config = analyzer.getConfig

    Dim selectAnalyzer As SqlFormatterSelectSubEAnalyzer
    Set selectAnalyzer = New SqlFormatterSelectSubEAnalyzer
    selectAnalyzer.Init analyzer

    Dim scanner As ObjArrayRefScanner
    Dim token As SqlToken
    Dim elem As ISqlFormatterElem
    Dim strArr As IPrimitiveArrayRef
    Dim stack As ObjStack

    ' ==============================
    ' 1. 扫描器内: 一个 UNION
    '    测试: 只有各组成部分的开头元素 + UNION 不会被解析
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getUnionToken)
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_UNION)
    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.tokenType = SqlTokenType.KEY_UNION

    ' ==============================
    ' 2. 扫描器内: From
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getFromToken)
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_FROM)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "from"

    ' ==============================
    ' 3. 扫描器内: Join
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getJoinToken)
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_JOIN)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "join"

    ' ==============================
    ' 4. 扫描器内: where
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken)
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_WHERE)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "where"

    ' ==============================
    ' 5. 扫描器内: on
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOnToken)
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_ON)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "on"

    ' ==============================
    ' 6. 扫描器内: group by
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken)
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_GROUP_BY)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "group by"

    ' ==============================
    ' 7. 扫描器内: having
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getHavingToken)
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_HAVING)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "having"

    ' ==============================
    ' 8. 扫描器内: order by
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken)
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_ORDER_BY)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "order by"

    ' ==============================
    ' 9. 扫描器内: limit
    '    测试: 只有各组成部分的开头元素
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getLimitToken)
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_LIMIT)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "limit"

    ' ==============================
    ' 10. 扫描器内: from
    '            + 一个表定义(tableName) + 一个注释 + 一个 partition
    '            + as + STR_NAME
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner( _
        getFromToken, _
        _
        getStrNameToken("tableX"), _
        createShareLineCommentToken("--c1"), _
        _
        getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getStrNameToken("t1"), _
        _
        createShareLineCommentToken("--c2"))
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_FROM)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "from"
    Debug.Assert strArr.Item(1) = "    tableX --c1"
    Debug.Assert strArr.Item(2) = "        partition (x1, x2)"
    Debug.Assert strArr.Item(3) = "    as t1 --c2"

    ' ==============================
    ' 11. 扫描器内: join
    '            + 一个表定义(tableName) + 一个注释 + 一个 partition
    '            + as + STR_NAME
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner( _
        getJoinToken, _
        _
        getStrNameToken("tableX"), _
        createShareLineCommentToken("--c1"), _
        _
        getPartitionToken, getLeftParenthesisToken, _
        getStrNameToken("x1"), getCommaToken, getStrNameToken("x2"), _
        getRightParenthesisToken, _
        _
        getAsToken, _
        _
        getStrNameToken("t1"), _
        _
        createShareLineCommentToken("--c2"))
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_JOIN)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 4
    Debug.Assert strArr.Item(0) = "join"
    Debug.Assert strArr.Item(1) = "    tableX --c1"
    Debug.Assert strArr.Item(2) = "        partition (x1, x2)"
    Debug.Assert strArr.Item(3) = "    as t1 --c2"

    ' ==============================
    ' 12. 扫描器内: where + STR_NAME + <= + STR_NAME
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getWhereToken, getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"))

    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_WHERE)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "where"
    Debug.Assert strArr.Item(1) = "    x1 <= x2"

    ' ==============================
    ' 13. 扫描器内: on + STR_NAME + <= + STR_NAME
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"))

    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_ON)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "on"
    Debug.Assert strArr.Item(1) = "    x1 <= x2"

    ' ==============================
    ' 14. 扫描器内: groupby + STR_NAME + ASC + [,] + STR_NAME + DESC + 一个注释
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getGroupByToken, _
        getStrNameToken("x1"), getAscToken, getCommaToken, _
        getStrNameToken("x2"), getDescToken, createShareLineCommentToken("--c1"))
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_ORDER_BY)
    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "group by"
    Debug.Assert strArr.Item(1) = "    x1 asc,"
    Debug.Assert strArr.Item(2) = "    x2 desc --c1"

    ' ==============================
    ' 15. 扫描器内: having + STR_NAME + <= + STR_NAME
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getHavingToken, getStrNameToken("x1"), getLeConditionOperator, _
        getStrNameToken("x2"))

    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_HAVING)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False

    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 2
    Debug.Assert strArr.Item(0) = "having"
    Debug.Assert strArr.Item(1) = "    x1 <= x2"

    ' ==============================
    ' 16. 扫描器内: orderBy + STR_NAME + ASC + [,] + STR_NAME + DESC
    '    测试: 各部分的基础组成
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getOrderByToken, _
        getStrNameToken("x1"), getAscToken, getCommaToken, _
        getStrNameToken("x2"), getDescToken)
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_ORDER_BY)
    ' 3. 检查结果
    Debug.Assert TypeOf elem Is SqlFormatterKeySectionEnv
    Debug.Assert scanner.hasNext = False
    Set strArr = elem.toStringArray(config)
    Debug.Assert strArr.Count = 3
    Debug.Assert strArr.Item(0) = "order by"
    Debug.Assert strArr.Item(1) = "    x1 asc,"
    Debug.Assert strArr.Item(2) = "    x2 desc"

    ' ==============================
    ' 17. 扫描器内: Limit + 一个数字 + offset + 一个数字 (LIMIT row_count OFFSET offset)
    ' ==============================
    Set token = getLimitToken
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(token, _
        createNumToken("12"), getOffsetToken, createNumToken("100"))

    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_LIMIT)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = False
    Debug.Assert elem.toString(config) = "limit 12 offset 100"

    ' ==============================
    ' 18. 扫描器内: STR_NAME
    '     测试: 遇到可解析的作用域之外的起始内容时，返回 null
    ' ==============================
    ' 1. 创建扫描器
    Set scanner = getObjArrayRefScanner(getStrNameToken("x1"))

    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.STR_NAME)

    ' 3. 检查结果
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext
    Debug.Assert token.Value = "x1"

    Debug.Assert elem Is Nothing

    ' ==============================
    ' 19. 扫描器内: on + DUPLICATE + KEY + UPDATE
    '            + UNION
    '    测试: 无法解析 on DUPLICATE 作用域
    ' ==============================
    ' 1. 创建一个扫描器
    Set scanner = getObjArrayRefScanner(getOnToken, getDuplicateToken, getKeyToken, getUpdateToken, _
        getUnionToken)
    ' 2. 执行测试方法
    Set elem = selectAnalyzer.ISqlFormatterSqlSubEnvAnalyzer_parseSubCompositionEnv(scanner, Nothing, SqlTokenType.KEY_ON)
    ' 3. 检查结果
    Debug.Assert elem Is Nothing
    Debug.Assert scanner.hasNext = True
    Set token = scanner.getNext()
    Debug.Assert token.tokenType = SqlTokenType.KEY_ON

End Sub